%%% -------------------------------------------------------------------
%%% Author  : maciek
%%% Description :
%%%
%%% Created : 16-04-2011
%%% -------------------------------------------------------------------
-module(call_controller).
-compile(export_all).
-behaviour(gen_server).
-include("iax2_common.hrl").
%% --------------------------------------------------------------------
%% Include files
%% --------------------------------------------------------------------

%% --------------------------------------------------------------------
%% External exports
%-export([]).

%% gen_server callbacks
%-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-record(state, {peerservername,
				srccallid,
				callcontrollername,
				fsmname,
				iaxusername,
				iaxpassword,
				iaxhostaddr,
				iaxportaddr,
				dstcallid,
				passchallenge,
				authmethods,
				start_ts}).

%% ====================================================================
%% External functions
%% ====================================================================


%% ====================================================================
%% Server functions
%% ====================================================================

%% --------------------------------------------------------------------
%% Function: init/1
%% Description: Initiates the server
%% Returns: {ok, State}          |
%%          {ok, State, Timeout} |
%%          ignore               |
%%          {stop, Reason}
%% --------------------------------------------------------------------
start_link(PeerServerName,SrcCallId,IAXUsername,IAXPassword,IAXHostAddr,IAXHostPort) ->
	CallCServerName = generate_server_name(SrcCallId,IAXHostAddr,IAXHostPort),
	gen_server:start_link({local, CallCServerName}, ?MODULE, [PeerServerName,SrcCallId,CallCServerName,IAXUsername,IAXPassword,IAXHostAddr,IAXHostPort], []),
	CallCServerName.
	
generate_server_name(SrcCallId,IAXHost,IAXPort) ->
	CallCServerStr = "call_controller_"++erlang:integer_to_list(SrcCallId),
	erlang:list_to_atom(CallCServerStr).

init([PeerServerName,SrcCallId,ServerName,IAXUsername,IAXPassword,IAXHostAddr,IAXHostPort]) ->
	io:format("Starting new CallController ~p with PeerServerName = ~p, SrcCallId = ~p~n",[ServerName,PeerServerName,SrcCallId]),
	%spawn FSM
	FSMName = erlang:list_to_atom(atom_to_list(ServerName)++"__fsm"),
	FSM = call_fsm:start_link(FSMName, ServerName),
    {ok, #state{peerservername=PeerServerName,srccallid=SrcCallId,callcontrollername=ServerName,fsmname=FSMName,iaxhostaddr=IAXHostAddr,
				iaxpassword=IAXPassword,iaxportaddr=IAXHostPort,iaxusername=IAXUsername}}.

hangedup(CallCName) ->
	io:format("Hanged up for cc name ~p~n",[CallCName]),
	gen_server:call(CallCName, hangedup).

place_call(CallControllerServerName,CallerId,CallerIdNum,CalledNum,Context) ->
	gen_server:call(CallControllerServerName, {place_call,[CallerId,CallerIdNum,CalledNum,Context]}).

stop(CallCName) ->
	gen_server:cast(CallCName, stop).
packet(CallControllerName,Packet) ->
	gen_server:cast(CallControllerName, {network_packet,Packet}).
send_authrep(CallControllerName) ->
	gen_server:cast(CallControllerName, sent_authrep).
send_ack(CallControllerName,ISeqNo,OSeqNo,TS) ->
	gen_server:cast(CallControllerName, {send_ack,ISeqNo,OSeqNo,TS}).
got_ougoing_media(CallControllerName,Media) ->
	gen_server:cast(CallControllerName, {got_ougoing_media,Media}).
send_media_fullframe(CallControllerName,Media) ->
	gen_server:cast(CallControllerName, {send_media_fullframe,Media}).




%% --------------------------------------------------------------------
%% Function: handle_call/3
%% Description: Handling call messages
%% Returns: {reply, Reply, State}          |
%%          {reply, Reply, State, Timeout} |
%%          {noreply, State}               |
%%          {noreply, State, Timeout}      |
%%          {stop, Reason, Reply, State}   | (terminate/2 is called)
%%          {stop, Reason, State}            (terminate/2 is called)
%% --------------------------------------------------------------------
handle_call({place_call,[CallerId,CallerIdNum,CalledNum,Context]},From,State) ->
	IE_Version =#ie_protocol_ver{version=2},
	IE_Called_Number=#ie_called_number{called_number=CalledNum},
	IE_Calling_Pres=#ie_callingpres{callingpres=allowed_not_screened},
	IE_Calling_Number=#ie_calling_number{calling_number=CallerId},
	%%TODO add Calling TON and Calling TNS 
	IE_Format=#ie_format{format=g_711_mu_law},
	IE_Username=#ie_username{username=State#state.iaxusername},
	IE_List=[IE_Version,IE_Called_Number,IE_Calling_Pres,IE_Calling_Number,IE_Format,IE_Username],
	FF = #fullframe{srccallnumber=State#state.srccallid,dstcallnumber=0,
					timestamp=0,r=0,c=0,
					oseqno=0,
					iseqno=0,
					frametype=16#06,
					subclass=16#01,
					data = IE_List},
	iax_network ! {send,State#state.iaxhostaddr,State#state.iaxportaddr,iax2_encoder:encode_full_frame(FF)},
	call_fsm:new_sent(State#state.fsmname),		
	call_fsm:sent_fullframe(State#state.fsmname),
	io:format("->>>> NEW~n"),
	NewState = State#state{start_ts=iax2_utils:get_timestamp()},
	{reply,ok,NewState};
	


handle_call(hangedup, From,State) ->
   
	iax_peer:call_hanged_up(State#state.peerservername, State#state.callcontrollername),
    {reply, ok, State};
handle_call(stop, From, State) ->
    
    {stop, normal, State};

handle_call(Request, From, State) ->
    Reply = ok,
    {reply, Reply, State}.

%% --------------------------------------------------------------------
%% Function: handle_cast/2
%% Description: Handling cast messages
%% Returns: {noreply, State}          |
%%          {noreply, State, Timeout} |
%%          {stop, Reason, State}            (terminate/2 is called)
%% --------------------------------------------------------------------
handle_cast(stop, State) ->
	io:format("==============CALL CONTROLLER TERMINATING=========~n"),
	{stop,normal,State};


handle_cast({network_packet,Packet=#fullframe{r=1}}, State) ->
	%io:format("RETRANSMITTED PACKET !!! ~p~n",[Packet]),
	{noreply,State};

handle_cast({network_packet,Packet=#fullframe{frametype=iax,subclass=authreq,srccallnumber=SrcCallId,data=IEList,oseqno=OSeqNo,iseqno=ISeqNo,timestamp=TS}}, State) ->
	call_fsm:got_fullframe(State#state.fsmname,iax,authreq,ISeqNo,OSeqNo,TS),
	{_,Challenge}=lists:keyfind(ie_challenge, 1, IEList),
	{_,AuthMethods}= lists:keyfind(ie_authmethods, 1, IEList),
	NewState = State#state{dstcallid=SrcCallId,passchallenge=Challenge,authmethods=AuthMethods},
	io:format("->>>>> AUTHREQ~n"),	
	call_fsm:got_authreq(State#state.fsmname),		
	{noreply,NewState};

handle_cast({network_packet,Packet=#fullframe{frametype=iax,subclass=accept,data=IEList,oseqno=OSeqNo,iseqno=ISeqNo,timestamp=TS}}, State) ->
	call_fsm:got_fullframe(State#state.fsmname,iax,accept,ISeqNo,OSeqNo,TS),
	call_fsm:got_accept(State#state.fsmname),		
	io:format("->>>> ACCEPT~n"),
	{noreply,State};
handle_cast({network_packet,Packet=#fullframe{frametype=iax,subclass=ping,data=IEList,oseqno=OSeqNo,iseqno=ISeqNo,timestamp=TS}}, State) ->
	io:format("->>>> PING~n"),
	call_fsm:got_fullframe(State#state.fsmname,iax,ping,ISeqNo,OSeqNo,TS),
	ISeqNoN = call_fsm:get_isec_number(State#state.fsmname),
	OSeqNoN = call_fsm:get_osec_number(State#state.fsmname),
	TSN = iax2_utils:get_timestamp()-State#state.start_ts,
	F = #fullframe{srccallnumber=State#state.srccallid,dstcallnumber=State#state.dstcallid,
					timestamp=TSN,r=0,c=0,
					oseqno=OSeqNoN,
					iseqno=ISeqNoN,
					frametype=16#06,
					subclass=16#03,
					data = []},
	io:format("->>>> PONG~n"),
	iax_network ! {send,State#state.iaxhostaddr,State#state.iaxportaddr,iax2_encoder:encode_full_frame(F)},
	call_fsm:sent_fullframe(State#state.fsmname),

	{noreply,State};

handle_cast({network_packet,Packet=#fullframe{frametype=iax,subclass=lagrq,data=IEList,oseqno=OSeqNo,iseqno=ISeqNo,timestamp=TS}}, State) ->
	io:format("->>>> LAGRQ~n"),
	call_fsm:got_fullframe(State#state.fsmname,iax,lagrq,ISeqNo,OSeqNo,TS),
	io:format("Got network LAGREQ responding LAGREP~n"),
	ISeqNoN = call_fsm:get_isec_number(State#state.fsmname),
	OSeqNoN = call_fsm:get_osec_number(State#state.fsmname),
	TSN = iax2_utils:get_timestamp()-State#state.start_ts,
	F = #fullframe{srccallnumber=State#state.srccallid,dstcallnumber=State#state.dstcallid,
					timestamp=Packet#fullframe.timestamp,r=0,c=0,
					oseqno=OSeqNoN,
					iseqno=ISeqNoN,
					frametype=16#06,
					subclass=16#0C,
					data = []},
	iax_network ! {send,State#state.iaxhostaddr,State#state.iaxportaddr,iax2_encoder:encode_full_frame(F)},
	io:format("<<<<-	 LAGRP~n"),
	call_fsm:sent_fullframe(State#state.fsmname),
	{noreply,State};

handle_cast({network_packet,Packet=#fullframe{frametype=control,subclass=answer,data=IEList,oseqno=OSeqNo,iseqno=ISeqNo,timestamp=TS}}, State) ->
	io:format("->>>>> ANSWER~n"),
	call_fsm:got_fullframe(State#state.fsmname,control,answer,ISeqNo,OSeqNo,TS),
	call_fsm:got_answer(State#state.fsmname),		
	{noreply,State};
handle_cast({network_packet,Packet=#fullframe{frametype=iax,subclass=hangup,data=IEList,oseqno=OSeqNo,iseqno=ISeqNo,timestamp=TS}}, State) ->
	io:format("->>>>> HANGUP~n"),
	call_fsm:got_fullframe(State#state.fsmname,control,answer,ISeqNo,OSeqNo,TS),
	call_fsm:got_hangup(State#state.fsmname),		
	{noreply,State};

handle_cast({network_packet,Packet=#fullframe{frametype=iax,subclass=ack,oseqno=OSeqNo,iseqno=ISeqNo,timestamp=TS}}, State) ->
	io:format("->>>> ACK ~p ~n",[Packet]),
	call_fsm:got_ack(State#state.fsmname,iax,ack,ISeqNo,OSeqNo,TS),
	{noreply,State};

handle_cast({network_packet,Packet=#fullframe{frametype=FT,subclass=SC,oseqno=OSeqNo,iseqno=ISeqNo,timestamp=TS}}, State) ->
		io:format("->>>> UNRECOGNIZED ~p ~n",[Packet]),
	call_fsm:got_fullframe(State#state.fsmname,FT,SC,ISeqNo,OSeqNo,TS),
	

	{noreply,State};
handle_cast({got_ougoing_media,Media},State) ->
	io:format("Got Media from Media Source~n"),
	call_fsm:got_ougoing_media(State#state.fsmname,Media),
	{noreply, State};
handle_cast(sent_authrep, State) ->
	io:format("<<<<<- AUTHREP~n"),
	ISeqNo = call_fsm:get_isec_number(State#state.fsmname),
	OSeqNo = call_fsm:get_osec_number(State#state.fsmname),
	ChallengeResult=iax2_utils:md5_challenge_response(State#state.iaxpassword, State#state.passchallenge),
	IEList=[#ie_md5_result{md5_result=ChallengeResult}],
	TS = iax2_utils:get_timestamp()-State#state.start_ts,
	F = #fullframe{srccallnumber=State#state.srccallid,dstcallnumber=State#state.dstcallid,
					timestamp=TS,r=0,c=0,
					oseqno=OSeqNo,
					iseqno=ISeqNo,
					frametype=16#06,
					subclass=16#09,
					data = IEList},
	iax_network ! {send,State#state.iaxhostaddr,State#state.iaxportaddr,iax2_encoder:encode_full_frame(F)},
	call_fsm:sent_fullframe(State#state.fsmname),
    {noreply, State};

handle_cast({send_ack,_ISeqNo,_OSeqNo,TS}, State) ->
	io:format("<<<<<- ACK~n"),

	ISeqNo = call_fsm:get_isec_number(State#state.fsmname),
	OSeqNo = call_fsm:get_osec_number(State#state.fsmname),
	
	F = #fullframe{srccallnumber=State#state.srccallid,dstcallnumber=State#state.dstcallid,
					timestamp=TS,r=0,c=0,
					oseqno=OSeqNo,
					iseqno=ISeqNo,
					frametype=16#06,
					subclass=16#04,
					data = []},
	iax_network ! {send,State#state.iaxhostaddr,State#state.iaxportaddr,iax2_encoder:encode_full_frame(F)},
	call_fsm:sent_ack(State#state.fsmname),
    {noreply, State};

handle_cast({send_media_fullframe,Media}, State) ->
	io:format("<<<<<- MEDIA FULLFRAME~n"),

	ISeqNo = call_fsm:get_isec_number(State#state.fsmname),
	OSeqNo = call_fsm:get_osec_number(State#state.fsmname),
	TS = iax2_utils:get_timestamp()-State#state.start_ts,
	F = #fullframe{srccallnumber=State#state.srccallid,dstcallnumber=State#state.dstcallid,
					timestamp=TS,r=0,c=0,
					oseqno=OSeqNo,
					iseqno=ISeqNo,
					frametype=16#02,
					subclass=16#4, %%speex
					data = Media},
	iax_network ! {send,State#state.iaxhostaddr,State#state.iaxportaddr,iax2_encoder:encode_full_frame(F)},
	call_fsm:sent_fullframe(State#state.fsmname),
    {noreply, State};


handle_cast(Msg, State) ->
    {noreply, State}.

%% --------------------------------------------------------------------
%% Function: handle_info/2
%% Description: Handling all non call/cast messages
%% Returns: {noreply, State}          |
%%          {noreply, State, Timeout} |
%%          {stop, Reason, State}            (terminate/2 is called)
%% --------------------------------------------------------------------
handle_info(Info, State) ->
    {noreply, State}.

%% --------------------------------------------------------------------
%% Function: terminate/2
%% Description: Shutdown the server
%% Returns: any (ignored by gen_server)
%% --------------------------------------------------------------------
terminate(Reason, State) ->
    ok.

%% --------------------------------------------------------------------
%% Func: code_change/3
%% Purpose: Convert process state when code is changed
%% Returns: {ok, NewState}
%% --------------------------------------------------------------------
code_change(OldVsn, State, Extra) ->
    {ok, State}.

%% --------------------------------------------------------------------
%%% Internal functions
%% --------------------------------------------------------------------

